package org.mmtk.plan.scale;

import org.mmtk.plan.Phase;
import org.mmtk.plan.StopTheWorldCollector;
import org.mmtk.plan.TraceLocal;
import org.mmtk.plan.scale.utils.ScaleLogger;
import org.mmtk.utility.Log;
import org.mmtk.utility.deque.AddressDeque;
import org.mmtk.utility.deque.ObjectReferenceDeque;
import org.mmtk.utility.options.Options;
import org.mmtk.vm.VM;
import org.vmmagic.pragma.Inline;
import org.vmmagic.pragma.Uninterruptible;

@Uninterruptible
public class ScaleCollector extends StopTheWorldCollector {
	protected boolean multiThreaded;
	protected ScaleTrace privateScaleTrace;
	protected ScaleTraceLocal privateTraceLocal;
	protected ScaleTraceLocal traceLocal;
	private ScaleTraceLocal currentTrace;
	private boolean concurrent = false;
		
	protected ScaleCollector() {	
		this.traceLocal = new ScaleTraceLocal("traceLocal", Scale.SCAN_MARK, global().scaleTrace);
		if(VM.activePlan.collectorCount() > 1) {
			this.multiThreaded = true;
			privateScaleTrace = new ScaleTrace(Scale.metaDataSpace);
			this.privateTraceLocal = new ScaleTraceLocal("privateTraceLocal", Scale.PRIVATE_SCAN_MARK, privateScaleTrace);		
		} else {
			this.multiThreaded = false;
			privateScaleTrace = null;
			this.privateTraceLocal = null;
		}		
	}

	/**
	 * Like in other plans
	 * @return
	 */
	@Inline
	private static Scale global() {
		return (Scale) VM.activePlan.global();
	}

	@Override
	public TraceLocal getCurrentTrace() {
		return this.currentTrace;
	}

	/**
	 * Primary thread is always the same for all phases
	 */
	@Inline
	public void collectionPhase(short phaseId, boolean primary) {
		if (Options.scaleDebug.getValue() > 5) {
			ScaleLogger.acquireLogLock();
			Log.prependThreadId();
			Log.write(" collectionPhase entered with phaseId=");Log.write(phaseId);
			Log.write(", phaseName=");Log.write(Phase.getName(phaseId));
			Log.write(", primary=");Log.write(primary);
			Log.write(", id=");Log.writeln(this.getId());
			Log.flush();
			ScaleLogger.releaseLogLock();
		}
		
		/**
		 * Called before GC cycle starts
		 */
		if (phaseId == Scale.PREPARE) {
			super.collectionPhase(phaseId, primary);
			if (Options.scaleDebug.getValue() > 5) {
				ScaleLogger.acquireLogLock();
				Log.prependThreadId();
				Log.writeln("collectionPhase prepare traceLocal");
				Log.flush();
				ScaleLogger.releaseLogLock();
			}
			this.traceLocal.prepare();
			if(multiThreaded) {
				if (Options.scaleDebug.getValue() > 3) {
					ScaleLogger.acquireLogLock();
					Log.prependThreadId();
					Log.writeln("collectionPhase prepare privateTraceLocal and make it current");
					Log.flush();
					ScaleLogger.releaseLogLock();
				}
				this.privateScaleTrace.prepare();
				this.privateTraceLocal.prepare();
				this.currentTrace = this.privateTraceLocal;
			} else {
				this.currentTrace = this.traceLocal;
			}
			return;
		}
		
		if (phaseId == Scale.PREPARE_STACKS) {
			super.collectionPhase(phaseId, primary);
			if (Options.scaleDebug.getValue() > 5) {
				this.currentTrace.printClosureCounts("collectionPhase, PREPARE_STACKS exit");
			}
			return;
		}
		
		if (phaseId == Scale.STACK_ROOTS) {
			super.collectionPhase(phaseId, primary);
			if (Options.scaleDebug.getValue() > 5) {
				this.currentTrace.printClosureCounts("colPhase(STACK_ROOTS) exit");
			}
			return;
		}
		
		if (phaseId == Scale.ROOTS) {
			super.collectionPhase(phaseId, primary);
			if (Options.scaleDebug.getValue() > 5) {
				this.currentTrace.printClosureCounts("colPhase(ROOTS) exit");
			}
			return;
		}

		/**
		 * Called after roots are collected to private structures
		 */
		if (phaseId == Scale.prepareClosure) {
			if (Options.scaleDebug.getValue() > 5) {
				this.traceLocal.printClosureCounts("CP(prepCl) before setting traceLocal CC");
			}
			this.traceLocal.setClosureCounts();
			if (Options.scaleDebug.getValue() > 5) {
				this.traceLocal.printClosureCounts("CP(prepCl) after setting traceLocal CC");
			}
			
			if(multiThreaded) {
				if (Options.scaleDebug.getValue() > 5) {
					ScaleLogger.acquireLogLock();
					Log.prependThreadId();
					Log.writeln("\tcollectionPhase - prepareClosure multithreaded, will use private trace");
					Log.flush();
					ScaleLogger.releaseLogLock();
				}
				if (Options.scaleDebug.getValue() > 5) {
					this.privateTraceLocal.printClosureCounts("CP(prepCl) before setting privateTL CC");
				}
				this.privateTraceLocal.setClosureCounts();
				if (Options.scaleDebug.getValue() > 5) {
					this.privateTraceLocal.printClosureCounts("CP(prepCl) after setting privateTL CC");
				}
				this.currentTrace = this.privateTraceLocal;
			}
						
			if (!Scale.isFirstClosure()) {
				if (Options.scaleDebug.getValue() > 5) {
					ScaleLogger.logConcMessage("CP(prepCl) not the first, restore saved roots and values");
				}		
				this.currentTrace.restorePreClosureState();
			}
			if (!Scale.isLastClosure()) {
				if (Options.scaleDebug.getValue() > 5) {
					ScaleLogger.logConcMessage("CP(prepCl) not the last, save collected roots and values for the next closure");
				}
				this.currentTrace.savePreClosureState(true);
			}
			if (multiThreaded) {
				if (Options.scaleDebug.getValue() > 5) {
					ScaleLogger.logConcMessage("CP(prepCl) multithreaded, may need to put together private values");
				}
				contributePrivateValues(primary);
			}
			
			this.concurrent = Scale.isConcurrentClosure();
			
			return;
		}

		if (phaseId == Scale.depthClosure) {
			if (primary) {
				if (Options.scaleDebug.getValue() > 5) {
					this.currentTrace.printClosureCounts("colPhase(depthCl) enter");
				}
				
				this.currentTrace.completeMetrixTrace(true, true, true);
				
				if (Options.scaleDebug.getValue() > 1) {
					this.currentTrace.printClosureCounts("colPhase(depthCl) exit");
				}
			}
			return;
		}

		if (phaseId == Scale.fixClosure) {
				if (Options.scaleDebug.getValue() > 5) {
					currentTrace.printClosureCounts("colPhase(fixCl) enter");
				}
				
				doFixClosure(primary);
				
				if (Options.scaleDebug.getValue() > 3) {
					currentTrace.printClosureCounts("colPhase(fixCl) exit");
				}			
			return;
		}

		if (phaseId == Scale.depthCheckClosure) {
			if (primary) {
				if (Options.scaleDebug.getValue() > 5) {
					this.currentTrace.printClosureCounts("colPhase(depthCheckCl) enter");
				}	
				
				Scale.inCheckClosure();
				doCheckClosure();
				
				if (Options.scaleDebug.getValue() > 3) {
					this.currentTrace.printClosureCounts("colPhase(depthCheckCl) exit");
				}		
			}
			return;
		}

		if (phaseId == Scale.regClosure) {
			if(this.concurrent) {
				if (Options.scaleDebug.getValue() > 5) {
					this.currentTrace.printClosureCounts("colPhase(regCheckCl) concurrent enter");
				}
				doRegularClosure();
			} else {
				if(primary) {
					if (Options.scaleDebug.getValue() > 3) {
						this.currentTrace.printClosureCounts("colPhase(regCheckCl) primary exit");
					}
					doRegularClosure();
				}
			}
			if (Options.scaleDebug.getValue() > 5) {
				this.currentTrace.printClosureCounts("colPhase(regCheckCl) exit");
			}
			return;
		}
		

	    if (phaseId == Scale.CLOSURE) {
	      this.currentTrace.completeTrace();
	      return;
	    }
		
		if (phaseId == Scale.RELEASE) {
			super.collectionPhase(phaseId, primary);
			traceLocal.release();
			if(this.multiThreaded) {
				this.privateTraceLocal.release();
				this.privateScaleTrace.release();
			}
			if(primary) {
				//output cycle data
				Log.writeln("Utilization before:");
				this.currentTrace.printDepthClosureCounts();
				//Log.writeln("Utilization after:");
				//this.currentTrace.printCheckClosureCounts();
			}
			return;
		}

		super.collectionPhase(phaseId, primary);
	}

	protected void doFixClosure(boolean primary) {
		if (VM.VERIFY_ASSERTIONS) {
			VM.assertions._assert(false,"doFixClosure must be implemented by a subclass");
		}
	}
	
	protected void doCheckClosure() {
		this.currentTrace.completeMetrixTrace(false, false, false);
	}
	
	protected void doRegularClosure() {
		this.currentTrace.completeTrace(false, true);
	}
	
	private void contributePrivateValues(boolean primary) {
		if (VM.VERIFY_ASSERTIONS) {
			VM.assertions._assert(Scale.collectorThreads > 1,
			"Scale.contributePrivateValues called for single treaded run");
			VM.assertions._assert(this.currentTrace == this.privateTraceLocal,
			"Scale.contributePrivateValues current trace must be the private one");
		}
		
		if (Options.scaleDebug.getValue() > 5) {
			ScaleLogger.acquireLogLock();
			Log.prependThreadId();
			Log.write("contributePrivateValues - using global structure traceLocal.getName()=");
			Log.writeln(traceLocal.getName());
			Log.flush();
			ScaleLogger.releaseLogLock();
		}
		ObjectReferenceDeque globalValues = this.traceLocal.getValues();
		AddressDeque globalRoots = this.traceLocal.getRoots();
		privateTraceLocal.makePrivateRootsPublic(globalRoots, globalValues);
		long privatePushed = privateTraceLocal.currentClosureCounts.getPushed();
		long privateMSPushed = privateTraceLocal.currentClosureCounts.getMsPushed();
		if(!Scale.isConcurrentClosure()) {
			if (Options.scaleDebug.getValue() > 5) {
				ScaleLogger.acquireLogLock();
				Log.prependThreadId();
				Log.writeln("contributePrivateValues - not a concurrent trace, updating global initial counters");
				Log.flush();
				ScaleLogger.releaseLogLock();
			}
			Scale.addInitialValues(privatePushed, privateMSPushed);
			if(!primary) {
				globalValues.flushLocal();
				//globalValues.resetLocal();
				globalRoots.flushLocal();
				//globalRoots.resetLocal();
			}
		} else {
			if (Options.scaleDebug.getValue() > 5) {
				ScaleLogger.acquireLogLock();
				Log.prependThreadId();
				Log.writeln("contributePrivateValues - concurrent trace, updating local initial counters");
				Log.flush();
				ScaleLogger.releaseLogLock();
			}
			this.traceLocal.currentClosureCounts.addPushed(privatePushed);
			this.traceLocal.currentClosureCounts.addMsPushed(privateMSPushed);
		}
		
		this.currentTrace = this.traceLocal;
	}
}
